﻿using System;
using System.Linq;
using System.ComponentModel.Composition;
using System.Reflection;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Processors.Parameter;

namespace Cubicle.Core.Processors.Invoke
{
    /// <summary>
    /// The Candidate selection processor serves two purposes
    /// 1) Select a candidate from the provided list which has parameters that match those supplied
    /// 2) Resolve the supplied parameters
    /// 
    /// This means that even if there is only one candidate, the CandidateSelectionProcessor still
    /// has to evaluate this candidate to resolve parameter compatibility
    /// </summary>
    [Export("Cubicle.Processors.Invoke.CandidateSelectionProcessor", typeof(IProcessor<InstructionRequest>))]
    [ExportMetadata("Name", "CandidateSelectionProcessor")]
    [ExportMetadata("Description", "Selects the approprivate method / constructor from the list of candidates")]
    public class CandidateSelectionProcessor : InstructionRequestProcessor
    {

        [Import]
        private ParameterProcessor _paramProcessor;

        [ImportingConstructor]
        public CandidateSelectionProcessor() : base(RequestType.AllMethod | RequestType.Construct) {}

        #region Overrides of Processor<InstructionRequest>

        public override InstructionRequest ConditionalProcess(InstructionRequest request)
        {
            foreach (var candidate in request.Candidates)
            {
                EvaluateCandidate(request, candidate);
                if (request.TargetMethod != null || request.TargetConstructor != null)
                    return request;
            }

            request.Fail("No member info found that match supplied parameters.");
            return request;
        }


        private void EvaluateCandidate<T>(InstructionRequest request, T candidate)
            where T : MemberInfo
        {
            // If the candidate is generic but no generic arguments were provided, evaluation fails.
            if (!request.IsGenericMethodInvoke 
                && candidate is MethodInfo
                && (candidate as MethodInfo).IsGenericMethod) return;

            // Handle generic arguments
            T current = candidate;
            if (request.IsGenericMethodInvoke)
            {
                current = MakeGenericMethod(request, candidate as MethodInfo, request.Inst.MethodArgs) as T;
                if (current == null) return;
            }

            // Process parameters
            var sourceParameters = request.Inst.Parameters;
            if (sourceParameters == null || sourceParameters.Length == 0)
            {
                if (request.Type().HasFlag(RequestType.Method))
                    request.TargetMethod = current as MethodInfo;
                else
                    request.TargetConstructor = current as ConstructorInfo;
                return;
            }

            // Extract ParameterInfo
            var parameterInfos = request.Type().HasFlag(RequestType.Method)
                                     ? (current as MethodInfo).GetParameters()
                                     : (current as ConstructorInfo).GetParameters();

            // Parameter mismatch
            if (parameterInfos.Length != sourceParameters.Length) return;

            // Get parameter types - transform ref parameters
            var results = parameterInfos.Select(x => x.ParameterType)
                                        .ToArray()
                                        .Process<object>(sourceParameters, _paramProcessor);

            // Failed to resolve
            if (results == null) return;

            // Successful resolve
            request.Parameters = results;

            // Set target
            if (request.Type().HasFlag(RequestType.Method))
                request.TargetMethod = current as MethodInfo;
            else
                request.TargetConstructor = current as ConstructorInfo;
        }

        private MethodInfo MakeGenericMethod(
            InstructionRequest request, MethodInfo target, object[] genericArguments)
        {
            // Perform sanity checks
            if (target.GetGenericArguments().Length != genericArguments.Length)
                return null;

            var results = genericArguments.Process<Type>(typeof (Type), _paramProcessor);
            target = target.MakeGenericMethod(results);
            return target;
        }

        #endregion
    }
}